\chapter{其他语言特性}\label{ch8}
在C++17中还有一些微小的核心语言特性的变更，将在这一章中介绍。

\section{嵌套命名空间}\label{ch8.1}
自从2003年第一次提出，到现在C++标准委员会终于同意了以如下方式定义嵌套的命名空间：
\begin{lstlisting}
    namespace A::B::C {
        ...
    }
\end{lstlisting}
等价于：
\begin{lstlisting}
    namespace A {
        namespace B {
            namespace C {
                ...
            }
        }
    }
\end{lstlisting}
注意目前还没有对嵌套内联命名空间的支持。
这是因为\texttt{inline}是作用于最内层还是整个命名空间还有歧义（两种情况都很有用）。

\section{有定义的表达式求值顺序}\label{ch8.2}
许多C++书籍里的代码如果按照直觉来看似乎是正确的，但严格上讲它们有可能导致未定义的行为。
一个简单的例子是在一个字符串中替换多个子串：
\begin{lstlisting}
    std::string s = "I heard it even works if you don't believe";
    s.replace(0, 8, "").replace(s.find("even", 4, "sometimes")
                       .replace(s.find("you don't"), 9, "I");
\end{lstlisting}
通常的假设是前8个字符被空串替换，\texttt{"even"}被\texttt{"sometimes"}替换，
\texttt{"you don't"}被\texttt{"I"}替换。因此结果是：
\begin{blacklisting}
    it sometimes works if I believe
\end{blacklisting}
然而在C++17之前最后的结果实际上并没有任何保证。因为查找子串位置的\texttt{find()}
函数可能在需要它们的返回值之前的任意时刻调用，而不是像直觉中的那样从左向右按顺序执行表达式。
事实上，所有的\texttt{find()}调用可能在执行第一次替换之前就全部执行，因此结果变为：

\begin{blacklisting}
    it even worsometimesf youIlieve
\end{blacklisting}
其他的结果也是有可能的：
\begin{blacklisting}
    it sometimes workIdon’t believe
    it even worsometiIdon’t believe
\end{blacklisting}
作为另一个例子，考虑使用输出运算符打印几个相互依赖的值：
\begin{lstlisting}
    std::cout << f() << g() << h();
\end{lstlisting}
通常的假设是依次调用\texttt{f()}、\texttt{g()}、\texttt{h()}函数。
然而这个假设实际上是错误的。\texttt{f()}、\texttt{g()}、\texttt{h()}有可能以任意顺序调用，
当这三个函数的调用顺序会影响返回值的时候可能就会出现奇怪的结果。

作为一个具体的例子，直到C++17之前，下面代码的行为都是未定义的：
\begin{lstlisting}
    i = 0;
    std::cout << ++i << ' ' << --i << '\n';
\end{lstlisting}
在C++17之前，它\emph{\textbf{可能}}会输出\texttt{1 0}，但也可能输出\texttt{0 -1}
或者\texttt{0 0}，这和变量\texttt{i}是\texttt{int}还是用户自定义类型无关（不过对于基本类型，
编译器一般会在这种情况下给出警告）。

为了解决这种未定义的问题，C++17标准重新定义了\emph{一些}运算符的的求值顺序，
因此这些运算符现在有了固定的求值顺序：
\begin{itemize}
    \item 对于运算
    \begin{lstlisting}
    e1 `\textbf{[}` e2 `\textbf{]}`
    e1 `\textbf{.}` e2
    e1 `\textbf{.*}` e2
    e1 `\textbf{->*}` e2
    e1 `\textbf{<<}` e2
    e1 `\textbf{>>}` e2
    \end{lstlisting}
    \emph{e1}现在保证一定会在\emph{e2}之前求值，因此求值顺序是从左向右。
    然而，注意同一个函数调用中的不同参数的计算顺序仍然是未定义的。也就是说：
    \begin{lstlisting}
    e1.f(a1, a2, a3);
    \end{lstlisting}
    中的\texttt{e1.f}保证会在\texttt{a1}、\texttt{a2}、\texttt{a3}之前求值。
    但\texttt{a1}、\texttt{a2}、\texttt{a3}的求值顺序仍是未定义的。
    \item 所有的赋值运算
    \begin{lstlisting}
    e2 `\textbf{=}` e1
    e2 `\textbf{+=}` e1
    e2 `\textbf{*=}` e1
    ...
    \end{lstlisting}
    中右侧的\emph{e1}现在保证一定会在左侧的\emph{e2}之前求值。
    \item 最后，类似于如下的\texttt{new}表达式
    \begin{lstlisting}
    `\textbf{new}` Type(e)
    \end{lstlisting}
    中保证内存分配的操作在对\emph{e}求值之前发生。
    新的对象的初始化操作保证在第一次使用该对象之前完成。
\end{itemize}
所有这些保证适用于所有基本类型和自定义类型。

因此，自从C++17起
\begin{lstlisting}
    std::string s = "I heard it even works if you don't believe";
    s.replace(0, 8, "").replace(s.find("even"), 4, "always")
                       .replace(s.find("don't believe"), 13, "use C++17");
\end{lstlisting}
保证将会把\texttt{s}的值修改为：
\begin{blacklisting}
    it always works if you use C++17
\end{blacklisting}
因为现在每个\texttt{find()}之前的替换操作都保证
会在对该\texttt{find()}表达式求值之前完成。

另一个例子，如下语句：
\begin{lstlisting}
    i = 0;
    std::cout << ++i << ' ' << --i << '\n';
\end{lstlisting}
对于任意类型的\texttt{i}都保证输出是\texttt{1 0}。

然而，其他大多数运算符的运算顺序仍然是未知的。例如：
\begin{lstlisting}
    i = i++ + i;    // 仍然是未定义的行为
\end{lstlisting}
这里，最右侧的\texttt{i}可能在\texttt{i}自增之前求值也可能在自增之后求值。

新的表达式求值顺序的另一个应用是定义一个在参数之前\hyperref[ch11.2.1]{插入空格的函数}。

\subsubsection{向后的不兼容性}
新的有定义的求值顺序可能会影响现有程序的输出。例如，考虑如下程序：
\inputcodefile{lang/evalexcept.cpp}
因为这个程序中的\texttt{vector<>}只有4个元素，因此在\texttt{print10elems()}的循环中
使用无效的索引调用\texttt{at()}时将会抛出异常：
\begin{lstlisting}
    std::cout << "value: " << v.at(i) << "\n";
\end{lstlisting}
在C++17之前，输出可能是：
\begin{blacklisting}
    value: 7
    value: 14
    value: 21
    value: 28
    EXCEPTION: ...
\end{blacklisting}
因为\texttt{at()}允许在输出\texttt{"value: "}之前调用，
所以当索引错误时可以跳过开头的\texttt{"value: "}输出。
\footnote{较旧版本的GCC或者Visual C++的行为就是这样的。}

自从C++17以后，输出保证是：
\begin{blacklisting}
    value: 7
    value: 14
    value: 21
    value: 28
    value: EXCEPTION: ...
\end{blacklisting}
因为现在\texttt{"value: "}的输出保证在\texttt{at()}调用之前。

\section{更宽松的用整型初始化枚举值的规则}\label{ch8.3}
对于一个有固定底层类型的枚举类型变量，自从C++17开始可以用一个整型值直接进行列表初始化。
这可以用于带有明确类型的无作用域枚举和所有有作用域的枚举，因为它们都有默认的底层类型：
\begin{lstlisting}
    // 指明底层类型的无作用域枚举类型
    enum MyInt : char { };
    MyInt i1{42};       // 自从C++17起OK（C++17以前ERROR）
    MyInt i2 = 42;      // 仍然ERROR
    MyInt i3(42);       // 仍然ERROR
    MyInt i4 = {42};    // 仍然ERROR

    // 带有默认底层类型的有作用域枚举
    enum class Weekday { mon, tue, wed, thu, fri, sat, sun };
    Weekday s1{0};      // 自从C++17起OK（C++17以前ERROR）
    Weekday s2 = 0;     // 仍然ERROR
    Weekday s3(0);      // 仍然ERROR
    Weekday s4 = {0};   // 仍然ERROR
\end{lstlisting}
如果\texttt{Weekday}有明确的底层类型的话结果完全相同：
\begin{lstlisting}
    // 带有明确底层类型的有作用域枚举
    enum class Weekday : char { mon, tue, wed, thu, fri, sat, sun };
    Weekday s1{0};      // 自从C++17起OK（C++17以前ERROR）
    Weekday s2 = 0;     // 仍然ERROR
    Weekday s3(0);      // 仍然ERROR
    Weekday s4 = {0};   // 仍然ERROR
\end{lstlisting}
对于\emph{没有}明确底层类型的无作用域枚举类型（没有\texttt{class}的\texttt{enum}），
你仍然不能使用列表初始化：
\begin{lstlisting}
    enum Flag { bit1=1, bit2=2, bit3=4 };
    Flag f1{0};     // 仍然ERROR
\end{lstlisting}
注意列表初始化不允许窄化，所以你不能传递一个浮点数：
\begin{lstlisting}
    enum MyInt : char { };
    MyInt i5{42.2}; // 仍然ERROR
\end{lstlisting}
一个定义新的整数类型的技巧是简单的定义一个以某个已有整数类型作为底层类型的枚举类型，
就像上面例子中的\texttt{MyInt}一样。
这个特性的动机之一就是为了支持这个技巧，如果没有这个特性，在不进行转换的情况下将无法初始化新的对象。

事实上自从C++17起标准库提供的\nameref{ch18}就直接使用了这个特性。

\section{修正\texttt{auto}类型的列表初始化}\label{ch8.4}
自从在C++11中引入了花括号\emph{统一}初始化之后，
每当使用\texttt{auto}代替明确类型进行列表初始化时就会出现一些和直觉不一致的结果：
\begin{lstlisting}
    int x{42};       // 初始化一个int
    int y{1, 2, 3};  // ERROR
    auto a{42};      // 初始化一个std::initializer_list<int>
    auto b{1, 2, 3}; // OK：初始化一个std::initializer_list<int>
\end{lstlisting}
这些\emph{直接}使用列表初始化（没有使用=）时的不一致行为现在已经被修复了。
因此如下代码的行为变成了：
\begin{lstlisting}
    int x{42};       // 初始化一个int
    int y{1, 2, 3};  // ERROR
    auto a{42};      // 现在初始化一个int
    auto b{1, 2, 3}; // 现在ERROR
\end{lstlisting}
注意这是一个\textbf{破坏性的更改(breaking change)}，因为它可能导致很多代码的行为在
无声无息中发生改变。因此，支持了这个变更的编译器现在即使在C++11模式下也会启用这个变更。
对于主流编译器，接受这个变更的版本分别是Visual Studio 2015，g++5，clang3.8。

注意当使用\texttt{auto}进行\emph{拷贝列表初始化}（使用了=）时仍然是初始化一个
\texttt{std::initializer\_list<>}：
\begin{lstlisting}
    auto c = {42};      // 仍然初始化一个std::initializer_list<int>
    auto d = {1, 2, 3}; // 仍然OK：初始化一个std::initializer_list<int>
\end{lstlisting}
因此，现在直接初始化（没有=）和拷贝初始化（有=）之间又有了显著的不同：
\begin{lstlisting}
    auto a{42};     // 现在初始化一个int
    auto c = {42};  // 仍然初始化一个std::initializer_list<int>
\end{lstlisting}
这也是更推荐使用直接列表初始化（没有=的花括号初始化）的原因之一。

\section{十六进制浮点数字面量}\label{ch8.5}
C++17允许指定十六进制浮点数字面量（有些编译器甚至在C++17之前就已经支持）。
当需要一个精确的浮点数表示时这个特性非常有用（如果直接用十进制的浮点数字面量不保证
存储的实际精确值是多少）。

例如：
\inputcodefile{lang/hexfloat.cpp}
程序通过使用已有的和新增的十六进制浮点记号定义了不同的浮点数值。
新的记号是一个以2为基数的科学记数法记号：
\begin{itemize}
    \item 有效数字/尾数用十六进制书写
    \item 指数部分用十进制书写，表示乘以2的n次幂
\end{itemize}
例如\texttt{0xAp2}的值为40（$10\times2^2$）。这个值也可以被写作\texttt{0x1.4p+5}，
也就是$1.25\times32$（0.4是十六进制的分数，等于十进制的0.25，$2^5=32$）。

程序的输出如下：
\begin{blacklisting}
    dec:     16  hex: 0x1p+4
    dec:     10  hex: 0x1.4p+3
    dec:     40  hex: 0x1.4p+5
    dec:      5  hex: 0x1.4p+2
    dec:      5  hex: 0x1.4p+2
    dec: 100000  hex: 0x1.86ap+16
    dec: 100000  hex: 0x1.86ap+16
    dec: 49.625  hex: 0x1.8dp+5
\end{blacklisting}
就像上例展示的一样，十六进制浮点数的记号很早就存在了，
因为输出流使用的\texttt{std::hexfloat}操作符自从C++11起就已经存在了。

\section{UTF-8字符字面量}\label{ch8.6}
自从C++11起，C++就已经支持以\texttt{u8}为前缀的UTF-8字符串字面量。
然而，这个前缀不能用于字符字面量。C++17修复了这个问题，所以现在可以这么写：
\begin{lstlisting}
    auto c = u8'6'; // UTF-8编码的字符6
\end{lstlisting}
在C++17中，\texttt{u8'6'}的类型是\texttt{char}，在C++20中可能会变为\texttt{char8\_t}，
因此这里使用\texttt{auto}会更好一些。

通过使用该前缀现在可以保证字符值是UTF-8编码。你可以使用所有的7位的US-ASCII字符，
这些字符的UTF-8表示和US-ASCII表示完全相同。
也就是说，\texttt{u8'6'}也是有效的以7位US-ASCII表示的字符'6'
（也是有效的ISO Latin-1、ISO-8859-15、基本Windows字符集中的字符）。
\footnote{ISO Latin-1的正式命名为ISO-8859-1，而为了包含欧元符号€引入的字符集
ISO-8859-15也被命名为ISO Latin-9。}
通常情况下你的源码字符被解释为US-ASCII或者UTF-8的结果是一样的，所以这个前缀并不是必须的。
\texttt{c}的值永远是\texttt{54}（十六进制\texttt{36}）。

这里给出一些背景知识来说明这个前缀的必要性：对于源码中的字符和字符串字面量，
C++标准化了你可以使用的字符而不是这些字符的值。这些值取决于\emph{源码字符集}。
当编译器为源码生成可执行程序时它使用\emph{运行字符集}。源码字符集几乎总是7位的
US-ASCII编码，而运行字符集通常是相同的。这意味着在任何C++程序中，所有相同的字符和字符串字面量
（不管有没有\texttt{u8}前缀）总是有相同的值。

然而，在一些特别罕见的场景中并不是这样的。例如，在使用EBCDIC字符集的旧的IBM机器上，字符'6'
的值将是246（十六进制为F6）。在一个使用EBCDIC字符集的程序中上面的字符\texttt{c}的值将是
246而不是54，如果在UTF-8编码的平台上运行这个程序可能会打印出字符ö，这个字符在ISO/IEC 8859-x
编码中的值为246.在这种情况下，这个前缀就是必须的。

注意\texttt{u8}只能用于单个字符，并且该字符的UTF-8编码必须只占一个字节。一个如下的初始化：
\begin{lstlisting}
    char c = u8'ö';
\end{lstlisting}
是不允许的，因为德语的曲音字符ö的UTF-8编码是两个字节的序列，分别是195和182（十六进制为C3 B6）。

因此，字符和字符串字面量现在接受如下前缀：
\begin{itemize}
    \item u8用于单字节US-ASCII和UTF-8编码
    \item u用于两字节的UTF-16编码
    \item U用于四字节的UTF-32编码
    \item L用于没有明确编码的宽字符，可能是两个或者四个字节
\end{itemize}

\section{异常声明作为类型的一部分}\label{ch8.7}
自从C++17之后，异常处理声明变成了函数类型的一部分。也就是说，如下的两个函数的类型是不同的：
\begin{lstlisting}
    void fMightThrow();
    void fNoexcept() noexcept;  // 不同类型
\end{lstlisting}
在C++17之前这两个函数的类型是相同的。这样的一个问题就是如果把一个可能抛出异常的函数赋给
一个保证不抛出异常的函数指针，那么调用时有可能会抛出异常：
\footnote{这样看起来好像是一个错误，但至少之前g++的确允许这种行为。}
\begin{lstlisting}
    void (*fp)() noexcept;  // 指向不抛异常的函数的指针
    fp = fNoexcept;         // OK
    fp = fMightThrow;       // 自从C++17起ERROR
\end{lstlisting}
把一个不会抛出异常的函数赋给一个可能抛出异常的函数指针仍然是有效的：
\begin{lstlisting}
    void (*fp2)();      // 指向可能抛出异常的函数的指针
    fp2 = fNoexcept;    // OK
    fp2 = fMightThrow;  // OK
\end{lstlisting}
因此，如果程序中只使用了没有\texttt{noexcept}声明的函数指针，那么将不会受该特性影响。
但请注意现在不能再违反函数指针中的\texttt{noexcept}声明（这可能会善意的破坏现有的程序）。

重载一个签名完全相同只有异常声明不同的函数是不允许的（就像不允许重载只有返回值不同的函数一样）：
\begin{lstlisting}
    void f3();
    void f3() noexcept;     // ERROR
\end{lstlisting}
注意其他的规则不受影响。例如，你仍然不能忽略基类中的\texttt{noexcept}声明：
\begin{lstlisting}
    class Base {
    public:
        virtual void foo() noexcept;
        ...
    };

    class Derived : public Base {
    public:
        void foo() override;    // ERROR：不能重载
        ...
    };
\end{lstlisting}
这里，派生类中的成员函数\texttt{foo()}和基类中的\texttt{foo()}类型不同所以不能重载。
这段代码不能通过编译，即使没有\texttt{override}修饰符代码也不能编译，因为我们不能用
更宽松的异常声明重载。

\subsubsection{使用传统的异常声明}
当使用传统的\texttt{noexcept}声明时，函数的是否抛出异常取决于条件为true还是false：
\begin{lstlisting}
    void f1();
    void f2() noexcept;
    void f3() noexcept(sizeof(int)<4);  // 和f1()或f2()的类型相同
    void f4() noexcept(sizeof(int)>=4); // 和f3()的类型不同
\end{lstlisting}
这里\texttt{f3()}的类型取决于条件的值：
\begin{itemize}
    \item 如果\texttt{sizeof(int)}返回4或者更大，签名等价于
    \begin{lstlisting}
    void f3() noexcept(false); // 和f1()类型相同
    \end{lstlisting}
    \item 如果\texttt{sizeof(int)}返回的值小于4，签名等价于
    \begin{lstlisting}
    void f3() noexcept(true);  // 和f2()类型相同
    \end{lstlisting}
\end{itemize}
因为\texttt{f4()}的异常条件和\texttt{f3()}的恰好相反，所以\texttt{f3()}和\texttt{f4()}
的类型总是不同（也就是说，它们一定是一个可能抛异常，另一个不可能抛异常）。

旧式的不抛异常的声明仍然有效但自从C++11起就被废弃：
\begin{lstlisting}
    void f5() throw();  // 和void f5() noexcept等价但已经被废弃
\end{lstlisting}
带参数的动态异常声明不再被支持（自从C++11起被废弃）：
\begin{lstlisting}
    void f6() throw(std::bad_alloc); // ERROR：自从C++17起无效
\end{lstlisting}

\subsubsection{对泛型库的影响}
将\texttt{noexcept}做为类型的一部分意味着会对泛型库产生一些影响。例如，下面的代码
直到C++14是有效的，但从C++17起不能再通过编译：
\inputcodefile{lang/noexceptcalls.cpp}
问题在于自从C++17起\texttt{f1()}和\texttt{f2()}的类型不再相同，
因此在实例化模板函数\texttt{call()时}编译器无法推导出类型\texttt{T}，

自从C++17起，你需要指定两个不同的模板参数来通过编译：
\begin{lstlisting}
    template<typename T1, typename T2>
    void call(T1 op1, T2 op2)
    {
        op1();
        op2();
    }
\end{lstlisting}
现在如果你想重载所有可能的函数类型，那么你需要重载的数量将是原来的两倍。
例如，对于标准库类型特征\texttt{std::is\_function<>}的定义，
主模板的定义如下，该模板用于匹配\texttt{T}不是函数的情况：
\begin{lstlisting}
    // 主模板（匹配泛型类型T不是函数的情况）：
    template<typename T> struct is_function : std::false_type { };
\end{lstlisting}
该模板从\hyperref[ch33.2]{\texttt{std::false\_type}}派生，
因此\texttt{is\_function<T>::value}对任何类型\texttt{T}都会返回\texttt{false}。

对于任何\emph{是}函数的类型，存在从\hyperref[ch33.2]{\texttt{std::true\_type}}
派生的部分特化版，因此成员\texttt{value}总是返回\texttt{true}：
\begin{lstlisting}
    // 对所有函数类型的部分特化版
    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...)> : std::true_type { };

    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) const> : std::true_type { };

    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) &> : std::true_type { };

    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) const &> : std::true_type { };
    ...
\end{lstlisting}
在C++17之前该特征总共有24个部分特化版本：因为函数类型可以用\texttt{const}
和\texttt{volatile}修饰符修饰，另外还可能有左值引用(\&)或右值引用(\&\&)修饰符，
还需要重载可变参数列表的版本。

现在在C++17中部分特化版本的数量变为了两倍，因为还需要为所有版本添加一个带\texttt{noexcept}
修饰符的版本：
\begin{lstlisting}
    ...
    // 对所有带有noexcept声明的函数类型的部分特化版本
    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) noexcept> : std::true_type { };

    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) const noexcept> : std::true_type { };

    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) & noexcept> : std::true_type { };

    template<typename Ret, typename... Params>
    struct is_function<Ret (Params...) const & noexcept> : std::true_type { };
    ...
\end{lstlisting}
那些没有实现\texttt{noexcept}重载的库可能在需要使用带有
\texttt{noexcept}的函数的场景中不能通过编译了。

\section{单参数\texttt{static\_assert}}\label{ch8.8}
自从C++17起，以前\texttt{static\_assert()}要求的用作错误信息的参数变为可选的了。
也就是说现在断言失败时输出的诊断信息完全依赖平台的实现。例如：
\begin{lstlisting}
    #include <type_traits>

    template<typename t>
    class C {
        // 自从C++11起OK
        static_assert(std::is_default_constructible<T>::value,
                      "class C: elements must be default-constructible");

        // 自从C++17起OK
        static_assert(std::is_default_constructible_v<T>);
        ...
    };
\end{lstlisting}
不带错误信息参数的新版本静态断言的示例也使用了\nameref{ch21.1}。

\section{预处理条件\texttt{\_\_has\_include}}\label{ch8.9}
C++17扩展了预处理，增加了一个检查某个头文件是否可以被包含的宏。例如：
\begin{lstlisting}
    #if __has_include(<filesystem>)
    #  include <filesystem>
    #  define HAS_FILESYSTEM 1
    #elif __has_include(<experimental/filesystem>)
    #  include <experimental/filesystem>
    #  define HAS_FILESYSTEM 1
    #  define FILESYSTEM_IS_EXPERIMENTAL 1
    #elif __has_include("filesystem.hpp")
    #  include "filesystem.hpp"
    #  define HAS_FILESYSTEM 1
    #  define FILESYSTEM_IS_EXPERIMENTAL 1
    #else
    #  define HAS_FILESYSTEM 0
    #endif
\end{lstlisting}
当相应的\texttt{\#include}指令有效时\texttt{\_\_has\_include(...)}会被求值为1。
其他的因素都不会影响结果（例如，相应的头文件是否已被包含过并不影响结果）。

另外，虽然求值为真可以说明相应的头文件确实存在但不能保证它的内容符合预期。
它的内容可能是空的或者无效的。

\texttt{\_\_has\_include}是一个纯粹的预处理指令。
所以不能将它用作源码里的条件表达式：
\begin{lstlisting}
    if (__has_include(<filesystem>)) {  // ERROR
    }
\end{lstlisting}

\section{后记}
\hyperref[ch8.1]{嵌套命名空间定义}由Jon Jagger于2003年在
\url{https://wg21.link/n1524}中首次提出。
Robert Kawulak于2014年在\url{https://wg21.link/n4026}中提出了新的提案。
最终被接受的是Robert Kawulak和Andrew Tomazos发表于\url{https://wg21.link/n4230}的提案。

\nameref{ch8.2}由Gabriel Dos Reis、Herb Sutter、Jonathan Caves在
\url{https://wg21.link/n4228}中首次提出。最终被接受的是Gabriel Dos Reis、
Herb Sutter和Jonathan Caves发表于\url{https://wg21.link/p0145r3}的提案。

\nameref{ch8.3}由Gabriel Dos Reis在\url{https://wg21.link/p0138r0}中首次提出。
最终被接受的是Gabriel Dos Reis发表于\url{https://wg21.link/p0138r2}的提案。

\nameref{ch8.4}由Ville Voutilainen在\url{https://wg21.link/n3681}以及
\url{https://wg21.link/3912}中首次提出。\texttt{auto}列表初始化最终的修正由
James Dennett在\url{https://wg21.link/n3681}提出。

\nameref{ch8.5}由Thomas Köppe在\url{https://wg21.link/p0245r0}中首次提出。
最终被接受的是Thomas Köppe发表于\url{https://wg21.link/p0245r1}的提案。

\nameref{ch8.6}是由Richard Smith在\url{https://wg21.link/n4197}中首次提出。
最终被接受的是Richard Smith发表于\url{https://wg21.link/n4267}的提案。

\nameref{ch8.7}由Jens Maurer在\url{https://wg21.link/n4320}中首次提出。
最终被接受的是Jens Maurer发表于\url{https://wg21.link/p0012r1}的提案。

\nameref{ch8.8}被接受的是Walter E. Brown发表于\url{https://wg21.link/n3928}的提案。

\hyperref[ch8.9]{预处理语句\texttt{\_\_has\_include()}}由Clark Nelson和Richard Smith在
\url{https://wg21.link/p0061r0}的某一部分中首次提出。
最终被接受的是Clark Nelson和Richard Smith发表于\url{https://wg21.link/p0061r1}的提案。
